﻿using System;
using System.Collections;
using System.Collections.Generic;
using SimpleExpressionEvaluator.Evaluation;

namespace SimpleExpressionEvaluator.Compilation.Functions.AggregateFunctions
{
    [NodeType(ExpressionNodeType.Function)]
    public abstract class AggregateFunction<T> : FunctionBase<T>
    {
        protected AggregateFunction()
        {
        }

        protected AggregateFunction(ExpressionNodeType nodeType) : base(nodeType)
        {
        }

       
        public override T Evaluate(IExpressionContext context)
        {
            var state = new AggregationState();

            if (Arguments.Count > 0)
            {
                object data = Arguments[0] != null ? Arguments[0].Evaluate(context) : null;
                state.DataSet = data as IEnumerable;
            }

            if (state.DataSet == null)
            {
                if (context.ContextObject is IEnumerable)
                    state.DataSet = context.ContextObject as IEnumerable;
                else
                    return default(T);
            }

            if (Arguments.Count > 1)
            {
                state.ValueExpression = Arguments[1];
            }

            var args = new object[0];
            if (Arguments.Count > 2)
            {
                args = new object[Arguments.Count - 2];
                for (int i = 2; i < Arguments.Count; i++)
                    args[i - 2] = Arguments[i].Evaluate(context);
            }

            return EvaluateFunction(context,state,args);
        }

        protected virtual T EvaluateFunction(IExpressionContext context,AggregationState state,params object[] args)
        {

            state.CanReturn = false;
            foreach (object dataItem in state.DataSet)
            {
                var childContext = new ChildExpressionContext(context, dataItem);
                object childVal = state.ValueExpression != null ? state.ValueExpression.Evaluate(childContext) : dataItem;

                state.DataItem = dataItem;

                AggregateValue(childVal, state, args);
                
                //allow function to short circuit if it already
                //knows the answer.
                if (state.CanReturn)
                    break;

                state.CurrentIndex++;
            }

            return ExtractAggregateValue(state);
        }

        protected abstract void AggregateValue(object value, AggregationState aggregationState, params object[] args);

        protected abstract T ExtractAggregateValue(AggregationState aggregationState);
    }

    public class AggregationState : Dictionary<string,object>
    {
        public AggregationState() : base(StringComparer.InvariantCultureIgnoreCase)
        {
            CurrentIndex = 0;
        }

        public object DataItem { get; set; }
        public IEnumerable DataSet { get; set; }
        public IExpression ValueExpression { get; set; }
        public bool CanReturn { get; set; }
        public int CurrentIndex { get; set; }

    }
}
